iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Software Development

一起看無間道學EdgeDB系列 第 23

[Day23] - 十幕:我想做個好人

  • 分享至 

  • xImage
  •  

Full schema preview

本日所有schema搶先看

劇情提要

建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後,心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明代表行禮。

scene10

此劇照引用自IMDb-無間道

重要提醒

本幕需要特別注意global current_user_id所屬objectPoliceRank是否合乎access policy!

EdgeQL query

設定global current_user_id

由於本場景需要多次操作PoliceSpy object,我們可以insert一個PoliceRankDCPPolice object,並將其id指定給global current_user_id

insert Police {name:= "test_DCP", police_rank:=PoliceRank.DCP};
set global current_user_id:= (
    select Police 
    filter .police_rank=PoliceRank.DCP limit 1).id;

update chen

將永仁的經典台詞加入到classic_lines property中。

update chen 
set {
    classic_lines := .classic_lines ++ ["對唔住,我係差人。"],
};

update lau

將建明的經典台詞加入到classic_lines property中。

update lau 
set {
    classic_lines := ["我以前無得揀,我而家想做好人。"],
};

insert ChenLauContact

這是本劇中,兩人最後一次聯絡了。

insert ChenLauContact {
    how:= "面對面",
    detail:= "建明與永仁相約於天台上談判",
    `when`:=  (insert FuzzyTime {
                fuzzy_year:=2002,
                fuzzy_month:=11,
                fuzzy_day:=27,
                fuzzy_hour:=15,
                fuzzy_minute:=0,
                fuzzy_second:=0,
            }),
    where:= (select Location filter .name="天台"),
};

insert真.林國平

真沒想到,國平竟然也是韓琛的臥底,第一次看到這段時,真是驚訝不已!

可是這麼一來,國平就不應該是Police而是GangsterSpy囉?我們應該刪掉國平 Police object,並新增一個國平 GangsterSpy object嗎?

這樣的話,之前國平 Police object的相關記錄都會被刪除(例如:CIBTeamTreat),這樣合理嗎?又或者我們應該重新去確認所有跟國平 Police object有關的object將其替換為國平 GangsterSpy object

該怎麼做其實沒有標準的答案,不過一個比較常見的方法是使用soft delete。使用一個類似is_activeproperty來表達該object的存取狀態,而不真正將其從資料庫中刪除。畢竟在最後一幕之前,我們的確不知道國平是臥底,國平 Police object是一個合適的表達。

最後我們insert國平的GangsterSpy object如下:

with b:= assert_single((select Police filter .name="林國平"))
insert GangsterSpy {
      name:= b.name,
      nickname:= b.nickname,
      police_rank:= b.police_rank,
      gangster_boss:= hon,
      dept:= b.dept,
      actors:= b.actors
};

感情線

在緊湊的臥底對決中,其實導演與編劇也穿插了一些感情戲份,讓我們一起來看看吧。

Mary & 建明

我們insertMary,並指定其為建明的lover

insert Character {
    name:= "Mary",
    eng_name:= "Mary",
    lover:= lau,
    actors:= (insert Actor{
        name:= "鄭秀文",
        eng_name:= "Sammi",
    }),
};

update lau 
set {
    lover:= assert_single((select Character filter .name="Mary")),
};

心兒 & 永仁

我們insert李心兒,並指定其為永仁的lover

insert Character {
    name:= "李心兒",
    lover:= chen,
    actors:= (insert Actor{
        name:= "陳慧琳",
        eng_name:= "Kelly",
    }),
};

update chen 
set {
    lover:= assert_single((select Character filter .name="李心兒")),
};

May & 永仁

現在我們面臨了一個有趣的情形,永仁看起來有兩個lover,但是我們的初始schema只設計了一個single linklover。我們現在需要將這個single linklover轉變為multi linklovers。這其中其實包含了兩步的變更,第一步是將lover重新命名為lovers,第二步是將loverssingle link改為multi link

您可以選擇做兩次migration,但其實EdgeDB相當聰明,大部份時間能夠猜中我們的意圖,讓我們試試用一步的migration來完成這個變化吧。我們變更Character如下:

type Character extending Person {
    classic_lines: array<str>;
    multi lovers: Character;
    multi actors: Actor;
}

接著於命令列執行edgedb migration create

did you drop link 'lover' of object type 'default::Character'? [y,n,l,c,b,s,q,?]
> n
did you rename link 'lover' of object type 'default::Character' to 'lovers'? [y,n,l,c,b,s,q,?]
> y
did you convert link 'lovers' of object type 'default::Character' to 'multi' cardinality? [y,n,l,c,b,s,q,?]
> y

留意第一個選項我們選擇了n,於是EdgeDB試著詢問我們。如果不是要drop的話,是否是要rename。如果是要rename的話,是否由single link改為multi link。如此一來,我們原來於lover中所指向的object,在於命令列執行edgedb migrate後也會一併帶到lovers

如果第一個選項我們選擇了y,EdgeDB會認為我們想先droplover,然候加上一個multi linklovers。如此一來lovers將會是空set,我們需要在於命令列執行完edgedb migrate後,手動將原來lover所指向的object加進來。

由這個例子可以知道,migration時不一定只能選擇y,應該視當下需求來決定。

由於此處進行了migration,所以需要再一次設定global current_user_id

set global current_user_id:= (
    select Police 
    filter .police_rank=PoliceRank.DCP limit 1).id;

最後我們insertMay,並將May加入到chenlovers

# end migration needs to be applied before running this query
insert Character{
    name:= "May",
    eng_name:= "May",
    lovers:= chen,
    actors:= (insert Actor{
        name:= "蕭亞軒",
        eng_name:= "Elva",
    }),
};

update chen 
set {
    lovers+= assert_single((select Character filter .name="May")),
};

我們可以確認chenlovers內確實有心兒及May。

select chen.lovers.name;
{'李心兒', 'May'}

斷捨離與detached

假設永仁覺得自己有太多lovers,想利用update幫他斷捨離,但卻發現有時候lovers會被設為空set,他百思不得其解,讓我們一起來看看永仁遇到的情況。永仁一共嘗試了下列五種query,只有query1會將lovers設為空set,query2~query5都可以成功將lovers設定為心兒一人:

query1
#❌
update Character filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
query2
#✅
update chen
set {lovers:= (select Character filter .name="李心兒")};
query3
#✅
with ch:= (select Character filter .name="陳永仁")
update ch
set {lovers:= (select Character filter .name="李心兒")};
query4
#✅
update PoliceSpy filter .name="陳永仁"
set {lovers:= (select Character filter .name="李心兒")};
query5
#✅
update Character filter .name="陳永仁"
set {lovers:= (select detached Character filter .name="李心兒")};

原來問題出在query1中,我們在update Characterset(關鍵字)內再次使用了select Character。這個Character將會是外面update Character filter .name="陳永仁"語法中的EdgeDBset,而不是Character這個object type。當想要在各種top-level EdgeQL statements(select, insert, updatedelete)內再次引用同一個object type時,需要使用detached

這是個很常見的錯誤,以上提供了四種修訂方法:

  • 如query2,使用alias,如chen
  • 如query3,於with區塊內,暫時命名一個變數,如ch
  • 如query4,於update時改使用其它object type,如PoliceSpy
  • 如query5,使用detached

insert此場景的Scene

insert Scene {
      title:= "我想做個好人", 
      detail:= "建明與永仁於天台相見,不料國平也趕到。永仁事先已報警,想持槍壓著" ++
               "建明到樓下交予警方。不料,於進電梯時被國平擊斃,原來他也是韓琛安" ++ 
               "裝於警隊的臥底。國平向建明表明身份,希望之後一起合作。但最終建明" ++
               "選擇於電梯中殺死國平,並營造永仁與國平雙雙死於槍戰的假象。事後," ++
               "心兒於葉校長遺物中發現永仁臥底檔案,恢復其警察身份,並由建明", ++
               "代表行禮。"
      who:=  (select Police filter .name="林國平") union 
             (select PoliceSpy filter .name="林國平") union
             {chen, lau},
      `when`:= assert_single(
                  (
                      select FuzzyTime 
                      filter .fuzzy_fmt="2002/11/27_15:00:00_ID"
                  )
              ),
      where:= (select Location filter .name="天台"),    
};

uuid選取object的技巧

假設我們想選擇一開始建立的PoliceRankDCPPolice object,該怎麼寫query呢?
最簡單的方法應該是filter .name="test_DCP"了吧,像是:

select Police filter .name="test_DCP";

但是假設我們只有該object str型態的id的話,又該怎麼選取呢?您可能會寫出以下query:

with pid:= <str>(select Police filter .name="test_DCP").id,
select Police filter .id=<uuid>pid;

但是除了這種經典的寫法外,EdgeDB還提供了以下寫法:

with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid;

pid此時為str型態的uuid,我們可以在前面使用<Police><uuid>casting而取得object

當想要使用上述寫法並搭配shape construction時,需加上(),例如:

# ✅
with pid:= <str>(select Police filter .name="test_DCP").id,
select (<Police><uuid>pid) {*};

而下面這兩種寫法是不被允許的:

# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select <Police><uuid>pid {*};
# ❌
with pid:= <str>(select Police filter .name="test_DCP").id,
select {*} <Police><uuid>pid;

另外,如果您已經有一個EdgeDBset,也可以進行類似的操作,例如:

with pid:= (select Police filter .name="test_DCP").id,
select <Police>pid;

最後清理

讓我們用上述技巧來刪除一開始建立的PoliceRankDCPPolice object,並reset global current_user_id

with pid:= <str>(select Police filter .name="test_DCP").id,
delete <Police><uuid>pid;

reset global current_user_id;

無間吹水

根據訪談,於拍攝時間只有華仔與編劇導演等少部份人知道,國平也是韓琛所派臥底,甚至連飾演國平的林家棟都是到最後一幕快開拍前才知道。當時他擔心前面的戲份是不是有演得不合劇情的地方,華仔說沒問題,他要的就是這種反差感。

參考資料

無間EdgeDB十幕:我想做個好人


上一篇
[Day22] - 九幕:真相大白
下一篇
[Day24] - EdgeDBSet概念加強
系列文
一起看無間道學EdgeDB30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言